#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
#include <semaphore.h>
#include <sys/types.h>

int main()
{
    // 创建内存共享对象
    char *shm_name_value = "shm_name_value";
    char *shm_name_sem = "shm_name_sem";

    // 为共享变量创建的共享内存对象
    int fd_value = shm_open(shm_name_value, O_CREAT | O_RDWR, 0644);
    // 为信号量创建的共享内存对象
    int fd_sem = shm_open(shm_name_sem, O_CREAT | O_RDWR, 0666);

    // 将共享变量区域扩充为100字节长度
    ftruncate(fd_value, 100);
    // 将共享信号量区域大小指定为sem_t所占空间
    ftruncate(fd_sem, sizeof(sem_t));

    // 映射共享变量区域到内存，并开启父子共享标签 偏移量选择0从头开始
    char *share_value = mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd_value, 0);
    // 映射共享信号量区域到内存，并开启父子共享标签 偏移量选择0从头开始
    sem_t *sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd_sem, 0);

    // 映射区建立完毕,释放文件描述符
    close(fd_value);
    close(fd_sem);

    // 初始化信号量 1：进程间共享 0：初始值
    sem_init(sem, 1, 0);

    // 创建子进程
    pid_t pid = fork();
    if (pid == 0) // 子进程
    {
        // 子进程先休眠1s
        sleep(1);

        // 子进程中：向共享内存中写入数据（此时产生将数据保存到物理内存）
        char *msg = "你是个好人!";
        strcpy(share_value, msg);

        printf("子进程向共享内存中写入数据：%s \n", msg);

        // 信号操作(也称：执行V操作)
        // 将信号量值加1，因为信号量阻塞的线程就会被唤醒
        sem_post(sem);
    }
    else if (pid > 0) // 父进程
    {
        // 等待信号量(也称：执行P操作)
        // 如果信号量>0, 信号量减1,函数返回向下执行
        // 如果信号量=0, 阻塞等待直至信号量>0
        sem_wait(sem);

        // 父进程读取共享内存中的数据，并输出
        printf("父进程读取子进程写入的数据：%s \n", share_value);

        // 等待所有子进程运行结束
        wait(NULL);

        // 释放共享内存
        munmap(share_value, 100);

        // 删除共享内存对象 /dev/shm/shm_test
        shm_unlink(shm_name_value);
    }

    // 父进程执行到此处，子进程已执行完毕，可以销毁信号量
    // 子进程执行到此处，父进程仍可能在等待信号量，此时销毁会导致未定义行为
    // 只有父进程中应该销毁信号量
    if (pid > 0)
    {
        sem_destroy(sem);
    }

    // 父子进程都应该解除映射，关闭文件描述符
    munmap(share_value, 100);
    munmap(sem, sizeof(sem));

    // shm_unlink只能调用一次，只在父进程中调用
    if (pid > 0)
    {
        shm_unlink(shm_name_value);
        shm_unlink(shm_name_sem);
    }

    return 0;
}